package log

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"time"
)

type LogLevel int

const callDepth = 2

const (
	LevelDebug LogLevel = iota
	LevelInfo
	LevelWarn
	LevelError
)

type log struct {
	entry Entry
}

func NewLog() *log {
	return &log{}
}

func (l *log) Debug(v ...interface{}) {
	l.log(LevelDebug, callDepth, v...)
}
func (l *log) Debugf(format string, v ...interface{}) {
	l.logf(LevelDebug, callDepth, format, v...)
}
func (l *log) Info(v ...interface{}) {
	l.log(LevelInfo, callDepth, v...)
}
func (l *log) Infof(format string, v ...interface{}) {
	l.logf(LevelInfo, callDepth, format, v...)
}
func (l *log) Warn(v ...interface{}) {
	l.log(LevelWarn, callDepth, v...)
}
func (l *log) Warnf(format string, v ...interface{}) {
	l.logf(LevelWarn, callDepth, format, v...)
}
func (l *log) Error(v ...interface{}) {
	l.log(LevelError, callDepth, v...)
}
func (l *log) Errorf(format string, v ...interface{}) {
	l.logf(LevelError, callDepth, format, v...)
}

func (l *log) log(level LogLevel, callDepth int, v ...interface{}) {
	l.entry.Level = level
	l.entry.Time.Time = time.Now()
	l.entry.Message = fmt.Sprint(v...)

	// if l.IsLogFileLine() {
	var ok bool
	_, l.entry.File, l.entry.Line, ok = runtime.Caller(callDepth)
	if !ok {
		l.entry.File = "???"
		l.entry.Line = 0
	}
	// }

	// fmt := e.logger.fmt // .GetFormatter()
	// printer := e.logger.printers[level]
	msg, err := l.entry.Format() // fmt(&e.entry)
	if err != nil {
		l.Errorf("log format error: %s", err.Error())
		return
	}
	var color = levelColors[level]
	fmt.Fprintf(os.Stdout, "\033[%s%s\033[0m", color, msg)
}

func (l *log) logf(level LogLevel, callDepth int, format string, v ...interface{}) {
	l.entry.Level = level
	l.entry.Time.Time = time.Now()
	l.entry.Message = fmt.Sprintf(format, v...)

	// if e.IsLogFileLine() {
	var ok bool
	_, l.entry.File, l.entry.Line, ok = runtime.Caller(callDepth)
	if !ok {
		l.entry.File = "???"
		l.entry.Line = 0
	}
	// }

	// fmt := e.logger.fmt // GetFormatter()
	// printer := e.logger.printers[level]
	msg, err := l.entry.Format() //  fmt(&e.entry)
	if err != nil {
		l.Errorf("log format error: %s", err.Error())
		return
	}

	var color = levelColors[level]
	fmt.Fprintf(os.Stdout, "\033[%s%s\033[0m", color, msg)
}

type Entry struct {
	File string  `json:"file"`
	Line int     `json:"line"`
	Time LogTime `json:"time"` // 转换
	// TimeStr        string      `json:"time"`  //
	Level LogLevel `json:"level"` // 转换
	// LevelStr       string      `json:"level"` //
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
	Err     error       `json:"-"` // 转换
	// hideCallStacks bool        `json:"-"` // 不需要输出
	Error string `json:"error"`
}

func (e *Entry) Format() ([]byte, error) {
	var err error
	buf := &bytes.Buffer{}
	if e.File != "" {
		// 等级 时间 文件 行号 信息
		_, err = buf.WriteString(fmt.Sprintf("[%5s] %s %s:%d\n%s", levels[e.Level], e.Time.Format(time.RFC3339), e.File, e.Line, e.Message))
		if err != nil {
			return nil, err
		}
	} else {
		// 等级 时间 信息
		_, err = buf.WriteString(fmt.Sprintf("[%5s] %s %s", levels[e.Level], e.Time.Format(time.RFC3339), e.Message))
		if err != nil {
			return nil, err
		}
	}

	if e.Data != nil {
		dataStr, err := serialize(e.Data)
		if err != nil {
			return nil, err
		}

		_, err = buf.WriteString("\nDATA: ")
		if err != nil {
			return nil, err
		}
		_, err = buf.Write(dataStr)
		if err != nil {
			return nil, err
		}
	}

	// errString := ""
	if e.Err != nil {
		// errString = e.Err.Error()
		_, err = buf.WriteString(fmt.Sprintf("\nERROR: %s", e.Err.Error()))
		if err != nil {
			return nil, err
		}
	}
	err = buf.WriteByte('\n')
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

var levels = []string{
	"debug", "info", "warn", "error",
}
var levelColors = []string{
	"36m", "32m", "33m", "31m",
}

func serialize(data interface{}) ([]byte, error) {
	if res, ok := data.(string); ok {
		return []byte(fmt.Sprintf("string: %s", res)), nil
	}
	res, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return nil, err
	}
	return res, nil
}

type LogTime struct {
	time.Time
}

func (t *LogTime) MarshalIndent() ([]byte, error) {
	return []byte(t.Format(time.RFC3339)), nil
}
